package cn.sylinx.hbatis.type;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.sylinx.hbatis.exception.ExecutingException;

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

	protected abstract JdbcType getJdbcType();

	private JdbcType getJdbcTypeInner() {
		JdbcType jdbcType = getJdbcType();
		if (jdbcType == null) {
			MappedJdbcTypes mappedJdbcTypes = getClass().getAnnotation(MappedJdbcTypes.class);
			JdbcType[] jdbcTypes = mappedJdbcTypes.value();
			if (jdbcTypes != null && jdbcTypes.length > 0) {
				jdbcType = jdbcTypes[0];
			}
		}
		return jdbcType;
	}

	@Override
	public void setParameter(PreparedStatement ps, int i, T parameter) throws SQLException {

		JdbcType jdbcType = getJdbcTypeInner();

		if (parameter == null) {

			if (jdbcType == null) {
				throw new TypeException(
						"JDBC requires that the JdbcType must be specified for all nullable parameters.");
			}

			try {
				ps.setNull(i, jdbcType.TYPE_CODE);
			} catch (SQLException e) {
				throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
						+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
						+ "Cause: " + e, e);
			}
		} else {
			try {
				setNonNullParameter(ps, i, parameter, jdbcType);
			} catch (Exception e) {
				throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType
						+ " . "
						+ "Try setting a different JdbcType for this parameter or a different configuration property. "
						+ "Cause: " + e, e);
			}
		}
	}

	@Override
	public T getResult(ResultSet rs, String columnName) throws SQLException {
		T result;
		try {
			result = getNullableResult(rs, columnName);
		} catch (Exception e) {
			throw new ExecutingException(
					"Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
		}
		if (rs.wasNull()) {
			return null;
		} else {
			return result;
		}
	}

	@Override
	public T getResult(ResultSet rs, int columnIndex) throws SQLException {
		T result;
		try {
			result = getNullableResult(rs, columnIndex);
		} catch (Exception e) {
			throw new ExecutingException(
					"Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
		}
		if (rs.wasNull()) {
			return null;
		} else {
			return result;
		}
	}

	@Override
	public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
		T result;
		try {
			result = getNullableResult(cs, columnIndex);
		} catch (Exception e) {
			throw new ExecutingException(
					"Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
		}
		if (cs.wasNull()) {
			return null;
		} else {
			return result;
		}
	}

	public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
			throws SQLException;

	public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

	public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

	public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}
